Дефиниция на конструктор
Един от най
тежко използуваните типове данни е string. В С++, обаче, низовете са произхождащ
тип (масив от символи) без вградени оператори за поддръжка. (Няма оператори за
присвояване или сравнение, например). Типът string е един неизбежен кандидат за
реализация като абстрактен тип данни. Ние ще използуваме проекта на класа
Sctring за да илюстрираме синтаксиса и семантиката на конструкторите,
деструкторите и презаредимите оператори. Започваме с по-подробно разглеждане на
конструкторите.
Един конструктор може да бъде идентифициран като за име
му бъде дадено името на класа. Той може да бъде презареждан за да предлага набор
от алтернативни инициализациии. Класът String, например, съдържа два члена
данни:
1. str, от тип char*, който адресира масива от символи на
низа.
2. len, от тип int, който съдържа дължината на символния масив, сочен
от str.
Нека да декларираме два конструктора за String, един, който
инициализира str и втори, който инициализира len.
class String {
public:
String( int );
String( char* );
private:
int
len;
char *str;
};
Дефиницията на един обект на класа отделя памет,
необходима нестатичните член данни, дефинирани от класа. Конструкторът осигурява
инициализацията на тази памет. Ето дефиницията на нашия първи конструктор на
String:
String::String( char *s ) {
// #include
<string.h>
len = strlen( s );
str = new char[ len + 1 ];
strcpy(
str, s );
}
Няма нищо особено сложно в дефиницията на този
конструктор. Силата й се дължи на мехонизма на класовете, който я извиква неявно
за всеки обект на клас, който потребителят дефинира с получаване на
инициализиращ символен низ. strlen() и strcpy() са функции за работа с низове от
стандартната С++ библиотека. Ние ще осигурим поддържане на подобни функции за
нашия клас String. Добре би било разгледаме защо str не присвоява просто адреса
на s:
str = s;
а й отделя динамично памет, в която копира s.
Основната причина е, че ние не можем да знаем дали е подходящо разположението на
паметта, отделена на низа, който s съдържа:
- Ако низът е локално
разположен, т.е. в работния стек, отделената памет ще бъде освободена когато
блокът, който я дефинира приключи работа - всяко по-нататъщно използуване на str
ще бъде погрешно. Например,
String *readString() {
// example of a
string with local extent
char inBuf[ maxLen ];
cin >>
inBuf;
String *ps = new Sting( inBuf );
return ps;
}
String *ps
= readString();
- Ако низът е динамично разположен, т.е. отделена му е
памет от свободната памет, той трябва да бъде изтрит преди обекта на класа да
излезе от обхват. А прилагането на оператора delete върху памет, която не е
отделена чрез оператора new може да предизвика сериозни грешки по време на
изпълнение на програмата. Изводът, който може да бъде направен от гореизложеното
е, че присвояването на стойност на указател, което не е съобразено с обхвата е
потенциално опасно и изисква особено внимание от страна на програмиста. Отново
ще се върнем на този проблем, относно указателите, когато разглеждаме
инициализацията и присвояването на обекти от един и същ клас. Ето дефиницията и
на втория конструктор на String:
String::String( int ln ) {
len =
ln;
str = new char[ len + 1 ];
str[0] = ‘�’;
}
Решението да се
поддържа дължината на низа като отделно поле може да бъде поставено под въпрос.
Трябва да се направи избор между загубата на памет за поддържане на това поле и
загубата на време за изчисляване на дължината. Изборът на първия вариант се
основава на две неща:
Първо, дължината на един низ се търси достатъчно
често, така че спестяването на време се предпочита пред спестяването на място (
това трябва да е доказано от практиката ).
Второ, обектите на String ще
бъдат използувани не само за поддържане на низове, но за дефиниране на буфери с
фиксирана дължина.
Понеже дефиницията на един обект на клас предизвиква
неявно обръщение към конструктор се прилага пълна проверка на типа на
дефиницията. Следните три дефиниции на обекти на String не са валидни. В първия
случай, не са зададени аргументи. Във втория аргументът е от неправилен тип, а в
третия има повече аргументи отколкото е необходимо.
int len =
1024;
String myString; // error: no arguments String
inBuf( &len ); //
error: bad type:
int* String search("rosebud", 7); //error: two
arguments
Съществуват две форми за синтактичен разбор на аргументите на
конструктора - явна и съкратена. Следните форми за дефиниране на обекти на класа
String са правилни:
// explicit form: reflects actual
invocation
String searchWord = String( "rosebud" );
// abbreviated
form: #1
String comonWord( "the" );
// abbreviated form: #2
String
inBuf = 1024;
// use of new required explicit form:
String *ptrBuf =
new String( 1024 );
Обръщението към оператора new извиква конструктора на
класа след като е отделена необходимата памет. Ако new не успее да отдели
необходимата памет конструкторът не се изпълнява. Указателят към класа получава
стойност 0. Например,
String *ptrBuf = new String( 1024 );
if ( ptrBuf
== 0 ) cerr << "free store exhaustedn";
Би било полезно обекти на
String да може да бъдат дефинирани и без задаване на аргументи.
Например,
String tmpStr;
Това може да бъде направено или чрез
задаване на стойности по подразбиране за аргументите на някой от вече
дефинираните конструктори, или чрез задаване на конструктор по подразбиране.
Конструкторът по подразбиране ще дефинира празен списък от аргументи. Масиви от
обекти на клас с конструктори използуват конструкторите при инициализация просто
както индивидуалните обекти на клас. Ако са дадени по-малко инициализатори,
отколкото са елементите на масива, се използуват конструкторите по подразбиране.
Ако такива не бъдат намирени инициализационният списък на масива трябва да бъде
пълен. Реализацията на конструктор по подразбиране за класа String може да
изглежда така:
#include "String.h"
String::String() {
//
default constructor
len = 0;
str = 0;
}
Една често срещана
програмна грешка е следната:
String st();
Този израз не дефинира
обект st от класа String, инициализиран с конструктора по подразбиране. По-скоро
той декларира функция st() без аргументи и тип за връщане обект на класа String.
Следват две правилни дефиниции на обект на класа String:
String
st;
String st = String();
Упражнение 7-1. Дефинирайте един конструктор
на String, който да приема следните декларации:
String s1( "rosebud", 7
);
String s1( "rosebud", 8 );
String s2( "", 1024 );
String s3("The Raw
and the Cooked" );
String s4;